package model.components.networks.sdn;

import java.util.ArrayList;
import java.util.List;

import model.components.links.link.Link;
import model.components.links.link.SdnLink;
import model.components.networks.NetworkGraph;
import model.components.nodes.node.Node;
import model.components.nodes.node.sdn.SdnRouter;
import model.components.nodes.node.sdn.SdnServer;
import model.components.nodes.node.sdn.SdnSwitch;
import edu.uci.ics.jung.graph.Graph;

public class SdnNetworks
{
	public static final String NAME_PREFIX = SdnNetwork.class.getSimpleName();
	public static final int NETWORKS_NUMBER = 1;
	
	public static final int MIN_ROUTERS_NUMBER = 20;
	public static final int MAX_ROUTERS_NUMBER = 20;
	
	public static final int MIN_SERVERS_NUMBER =  0;
	public static final int MAX_SERVERS_NUMBER =  0;
	
	public static final int MIN_SWITCHES_NUMBER =  0;
	public static final int MAX_SWITCHES_NUMBER =  0;
	
	public static final int LINUX_PERCENTAGE = 100;
	public static final int WINDOWS_PERCENTAGE = 0;
	public static final int SOLARIS_PERCENTAGE = 0;
	public static final int ANDROID_PERCENTAGE = 0;
	
	public static final int MIN_CPU = 20;
	public static final int MAX_CPU = 20;
	
	public static final int MIN_CPU_PRICE = 50;
	public static final int MAX_CPU_PRICE = 100;
		
	public static final int MIN_RAM = 20;
	public static final int MAX_RAM = 20;
	
	public static final int MIN_RAM_PRICE = 50;
	public static final int MAX_RAM_PRICE = 100;
	
	public static final int MIN_STORAGE = 10;
	public static final int MAX_STORAGE = 10;

	public static final int MIN_STORAGE_PRICE = 50;
	public static final int MAX_STORAGE_PRICE = 100;
		
	public static final int MIN_VIRTUAL_NODES_NUMBER = 20;
	public static final int MAX_VIRTUAL_NODES_NUMBER = 20;
	
	public static final int LINKS_NUMBER = 100;
	
	//public static final int MIN_BW = 5000;
	//public static final int MAX_BW = 5000;
	
	public static final int MIN_BW = 100;
	public static final int MAX_BW = 100;
	
	public static final int MIN_BW_PRICE =  50;
	public static final int MAX_BW_PRICE = 100;
	
	//public static final int MIN_DISTANCE =  50;
	//public static final int MAX_DISTANCE = 500;
	
	public static final int MIN_DISTANCE =  1;
	public static final int MAX_DISTANCE = 10;
	
	private List< SdnNetwork > sdnNetworks;
	public List< SdnNetwork > getSdnNetworks(){ return sdnNetworks; }
	public void setSdnNetworks( List< SdnNetwork > sdnNetworks ){ this.sdnNetworks = sdnNetworks; }
	
	public SdnNetworks()
	{
		List< SdnNetwork > sdnNetworks = this.sdnNetworks( NAME_PREFIX, NETWORKS_NUMBER, MIN_ROUTERS_NUMBER, MAX_ROUTERS_NUMBER, MIN_SWITCHES_NUMBER, MAX_SWITCHES_NUMBER, MIN_SERVERS_NUMBER, MAX_SERVERS_NUMBER, LINUX_PERCENTAGE, WINDOWS_PERCENTAGE, SOLARIS_PERCENTAGE, ANDROID_PERCENTAGE, MIN_CPU, MAX_CPU, MIN_CPU_PRICE, MAX_CPU_PRICE, MIN_RAM, MAX_RAM, MIN_RAM_PRICE, MAX_RAM_PRICE, MIN_STORAGE, MAX_STORAGE, MIN_STORAGE_PRICE, MAX_STORAGE_PRICE, MAX_VIRTUAL_NODES_NUMBER, MIN_BW, MAX_BW, MIN_BW_PRICE, MAX_BW_PRICE, MIN_DISTANCE, MAX_DISTANCE );
		this.setSdnNetworks( sdnNetworks );
	}
	
	public SdnNetworks( String namePrefix, int networksNumber, int minRoutersNumber, int maxRoutersNumber, int minSwitchesNumber, int maxSwitchesNumber, int minServersNumber, int maxServersNumber, int linuxPercentage, int windowsPercentage, int solarisPercentage, int androidPercentage, int minCPU, int maxCPU, int minCPUprice, int maxCPUprice, int minRAM, int maxRAM, int minRAMprice, int maxRAMprice, int minStorage, int maxStorage, int minStoragePrice, int maxStoragePrice, int maxVirtualNodesNumber, int minBW, int maxBW, int minBWprice, int maxBWprice, int minDistance, int maxDistance )
	{
		List< SdnNetwork > sdnNetworks = this.sdnNetworks( namePrefix, networksNumber, minRoutersNumber, maxRoutersNumber, minSwitchesNumber, maxSwitchesNumber, minServersNumber, maxServersNumber, linuxPercentage, windowsPercentage, solarisPercentage, androidPercentage, minCPU, maxCPU, minCPUprice, maxCPUprice, minRAM, maxRAM, minRAMprice, maxRAMprice, minStorage, maxStorage, minStoragePrice, maxStoragePrice, maxVirtualNodesNumber, minBW, maxBW, minBWprice, maxBWprice, minDistance, maxDistance );
		this.setSdnNetworks( sdnNetworks );
	}
	
	public List< SdnNetwork > sdnNetworks( String namePrefix, int networksNumber, int minRoutersNumber, int maxRoutersNumber, int minSwitchesNumber, int maxSwitchesNumber, int minServersNumber, int maxServersNumber, int linuxPercentage, int windowsPercentage, int solarisPercentage, int androidPercentage, int minCPU, int maxCPU, int minCPUprice, int maxCPUprice, int minRAM, int maxRAM, int minRAMprice, int maxRAMprice, int minStorage, int maxStorage, int minStoragePrice, int maxStoragePrice, int maxVirtualNodesNumber, int minBW, int maxBW, int minBWprice, int maxBWprice, int minDistance, int maxDistance )
	{
		List< SdnNetwork > sdnNetworks = new ArrayList< SdnNetwork >();
		
		for( int i = 0; i < networksNumber; i++ )
		{
			String nameNetwork = namePrefix + i;
			Graph< Node, Link > graph = new NetworkGraph();
			int nodeCount = 0;
			
			int sdnRoutersNumber = this.randomMinMaxNumber( minRoutersNumber, maxRoutersNumber );
						
			List< SdnRouter > sdnRouters = new ArrayList< SdnRouter >();
			
			for( int j = 0; j < sdnRoutersNumber; j++ )
			{
				String os = this.randomOS( linuxPercentage, windowsPercentage, solarisPercentage, androidPercentage );
				int cpu = this.randomMinMaxNumber( minCPU, maxCPU );
				int ram = this.randomMinMaxNumber( minRAM, maxRAM );
				int virtualNodesNumber = this.randomMinMaxNumber( MIN_VIRTUAL_NODES_NUMBER, MAX_VIRTUAL_NODES_NUMBER );
				SdnRouter sdnRouter = new SdnRouter( nameNetwork, nodeCount++, os, cpu, ram, virtualNodesNumber );
				sdnRouters.add( sdnRouter );
				graph.addVertex( sdnRouter );
			}
						
			int sdnSwitchesNumber = this.randomMinMaxNumber( minSwitchesNumber, maxSwitchesNumber );
			List< SdnSwitch > sdnSwitches = new ArrayList< SdnSwitch >();
			
			for( int j = 0; j < sdnSwitchesNumber; j++ )
			{
				String os = this.randomOS( linuxPercentage, windowsPercentage, solarisPercentage, androidPercentage );
				int cpu = this.randomMinMaxNumber( minCPU, maxCPU );
				int ram = this.randomMinMaxNumber( minRAM, maxRAM );
				SdnSwitch sdnSwitch = new SdnSwitch( nameNetwork, nodeCount++, os, cpu, ram, maxVirtualNodesNumber );
				sdnSwitches.add( sdnSwitch );
				graph.addVertex( sdnSwitch );
			}
			
			int sdnServersNumber = this.randomMinMaxNumber( minServersNumber, maxServersNumber );
			List< SdnServer > sdnServers = new ArrayList< SdnServer >();
			
			for( int j = 0; j < sdnServersNumber; j++ )
			{
				String os = this.randomOS( linuxPercentage, windowsPercentage, solarisPercentage, androidPercentage );
				int cpu = this.randomMinMaxNumber( minCPU, maxCPU );
				int ram = this.randomMinMaxNumber( minRAM, maxRAM );
				int storage = this.randomMinMaxNumber( minStorage, maxStorage );
				SdnServer sdnServer = new SdnServer( nameNetwork, nodeCount++, os, cpu, ram, storage, maxVirtualNodesNumber );
				sdnServers.add( sdnServer );
				graph.addVertex( sdnServer );
			}
			
			this.connectSdnRouters( graph, sdnRouters, nameNetwork );
			
			int linkCount = 0;
			/*
			for( SdnRouter sdnRouter: sdnRouters )
			{
				List< SdnRouter > randomSdnRouters = this.randomSdnRouters( sdnRouters );
				
				for( SdnRouter randomSdnRouter: randomSdnRouters )
					if( sdnRouter.getNumber() < randomSdnRouter.getNumber() )
					{
						int bw = this.randomMinMaxNumber( minBW, maxBW );
						int distance = this.randomMinMaxNumber( minDistance, maxDistance );
						//int bw = 100;
						//int distance = 100;
						SdnLink sdnLink = new SdnLink( nameNetwork, linkCount++, bw, distance );
						graph.addEdge( sdnLink, sdnRouter, randomSdnRouter );
					}
			}*/
			
			/*
			for( SdnRouter sdnRouter: sdnRouters )
				for( SdnRouter otherSdnRouter: sdnRouters )
					if( sdnRouter.getNumber() < otherSdnRouter.getNumber() )
					{
						int bw = this.randomMinMaxNumber( minBW, maxBW );
						int distance = this.randomMinMaxNumber( minDistance, maxDistance );
						SdnLink sdnLink = new SdnLink( nameNetwork, linkCount++, bw, distance );
						graph.addEdge( sdnLink, sdnRouter, otherSdnRouter );
					}
			*/
			
			for( SdnSwitch sdnSwitch: sdnSwitches )
				for( SdnSwitch otherSdnSwitch: sdnSwitches )
					if( sdnSwitch.getNumber() < otherSdnSwitch.getNumber() )
					{
						//int bw = this.randomMinMaxNumber( minBW, maxBW );
						//int distance = this.randomMinMaxNumber( minDistance, maxDistance );
						int bw = 100;
						int distance = 10;
						SdnLink sdnLink = new SdnLink( nameNetwork, linkCount++, bw, distance );
						graph.addEdge( sdnLink, sdnSwitch, otherSdnSwitch );
					}
							
			for( SdnServer sdnServer: sdnServers )
			{	
				List< SdnSwitch > randomSdnSwitches = this.randomSdnSwitches( sdnSwitches );
				
				for( SdnSwitch randomSdnSwitch: randomSdnSwitches )
				{
					//int bw = this.randomMinMaxNumber( minBW, maxBW );
					//int distance = this.randomMinMaxNumber( minDistance, maxDistance );
					int bw = 100;
					int distance = 10;
					SdnLink sdnLink = new SdnLink( nameNetwork, linkCount++, bw, distance );
					graph.addEdge( sdnLink, sdnServer, randomSdnSwitch );
				}
			}
						
			int cpuPrice = this.randomMinMaxNumber( minCPUprice, maxCPUprice );
			int ramPrice = this.randomMinMaxNumber( minRAMprice, maxRAMprice );
			int storagePrice = this.randomMinMaxNumber( minStoragePrice, maxStoragePrice );	
			int bwPrice = this.randomMinMaxNumber( minBWprice, maxBWprice );
			
			sdnNetworks.add( new SdnNetwork( namePrefix, i, graph, cpuPrice, ramPrice, storagePrice, bwPrice ) );
		}
		
		return sdnNetworks;
	}
	private int randomMinMaxNumber( int minNumber, int maxNumber )
	{
		return minNumber + (int) ( Math.random() * ( maxNumber - minNumber + 1 ) );
	}
	private String randomOS( int linuxPercentage, int windowsPercentage, int solarisPercentage, int androidPercentage )
	{
		double randomOS = 100 * Math.random();
		
		if( 0 <= randomOS && randomOS < linuxPercentage )
			return Node.LINUX_OS;
		
		else if( linuxPercentage <= randomOS && randomOS < linuxPercentage + windowsPercentage )
			return Node.WINDOWS_OS;
		
		else if( linuxPercentage + windowsPercentage <= randomOS && randomOS < linuxPercentage + windowsPercentage + solarisPercentage )
			return Node.SOLARIS_OS;
		
		else if( linuxPercentage + windowsPercentage + solarisPercentage <= randomOS && randomOS < 100 )
			return Node.ANDROID_OS;
		
		return null;
	}
	private List< SdnRouter > randomSdnRouters( List< SdnRouter > sdnRouters )
	{
		List< SdnRouter > randomSdnRouters = new ArrayList< SdnRouter >();
		List< Integer > selectedIndexes = new ArrayList< Integer >();
		//int randomSdnRoutersNumber = this.randomMinMaxNumber( 1, sdnRouters.size() );
		//int randomSdnRoutersNumber = sdnRouters.size();
		int randomSdnRoutersNumber = 5;
		int numberOk = 0;
		
		while( numberOk < randomSdnRoutersNumber )
		{
			int randomIndex = this.randomMinMaxNumber( 0, sdnRouters.size() - 1 );
			boolean isSelected = false;
			
			for( int selectedIndex: selectedIndexes )
				if( selectedIndex == randomIndex )
				{
					isSelected = true;
					break;
				}
			
			if( !isSelected )
			{
				selectedIndexes.add( randomIndex );
				randomSdnRouters.add( sdnRouters.get( randomIndex ) );
				numberOk++;
			}
		}
		
		return randomSdnRouters;
	}
	private List< SdnSwitch > randomSdnSwitches( List< SdnSwitch > sdnSwitches )
	{
		List< SdnSwitch > randomSdnSwitches = new ArrayList< SdnSwitch >();
		List< Integer > selectedIndexes = new ArrayList< Integer >();
		//int randomSdnSwitchesNumber = this.randomMinMaxNumber( 1, sdnSwitches.size() );
		int randomSdnSwitchesNumber = sdnSwitches.size();
		int numberOk = 0;
		
		while( numberOk < randomSdnSwitchesNumber )
		{
			int randomIndex = this.randomMinMaxNumber( 0, sdnSwitches.size() - 1 );
			boolean isSelected = false;
			
			for( int selectedIndex: selectedIndexes )
				if( selectedIndex == randomIndex )
				{
					isSelected = true;
					break;
				}
			
			if( !isSelected )
			{
				selectedIndexes.add( randomIndex );
				randomSdnSwitches.add( sdnSwitches.get( randomIndex ) );
				numberOk++;
			}
		}
		
		return randomSdnSwitches;
	}
	
	private void connectSdnRouters( Graph< Node, Link > graph, List< SdnRouter > sdnRouters, String nameNetwork )
	{	
		List< SdnRouter > sortedSdnRouters = new ArrayList< SdnRouter >();
		sortedSdnRouters.addAll( sdnRouters );
		
		List< SdnRouter > randomSdnRouters = new ArrayList< SdnRouter >();
		
		while( sortedSdnRouters.size() > 0 )
		{
			int randomNumber = this.randomMinMaxNumber( 0, sortedSdnRouters.size() - 1 );
			randomSdnRouters.add( sortedSdnRouters.get( randomNumber ) );
			sortedSdnRouters.remove( randomNumber );
		}
		
		int linksCount = 0;
		
		for( int i = 0; i < randomSdnRouters.size(); i++ )
		{
			SdnRouter sdnRouter1 = null;
			SdnRouter sdnRouter2 = null;
			
			if( i == randomSdnRouters.size() - 1 )
			{
				sdnRouter1 = randomSdnRouters.get( randomSdnRouters.size() - 1 );
				sdnRouter2 = randomSdnRouters.get( 0 );
			}
			else
			{
				sdnRouter1 = randomSdnRouters.get( i );
				sdnRouter2 = randomSdnRouters.get( i + 1 );
			}
			
			int bw = this.randomMinMaxNumber( MIN_BW, MAX_BW );
			
			//int distance = this.randomMinMaxNumber( MIN_DISTANCE, MAX_DISTANCE );
			int distance = this.randomMinMaxNumber( MIN_DISTANCE, MAX_DISTANCE ) / 10;
			
			SdnLink sdnLink = new SdnLink( nameNetwork, linksCount++, bw, distance );
			sdnLink.setFirstNode( sdnRouter1 );
			sdnLink.setSecondNode( sdnRouter2 );
			graph.addEdge( sdnLink, sdnRouter1, sdnRouter2 );
		}
		
		
		
		while( linksCount < LINKS_NUMBER )
		{
			SdnRouter sdnRouter1 = randomSdnRouters.get( this.randomMinMaxNumber( 0, randomSdnRouters.size() - 1 ) );
			SdnRouter sdnRouter2 = randomSdnRouters.get( this.randomMinMaxNumber( 0, randomSdnRouters.size() - 1 ) );
			
			if( !sdnRouter1.equals( sdnRouter2 ) && !this.isConnected( sdnRouter1, sdnRouter2, graph ) )
			{
				int bw = this.randomMinMaxNumber( MIN_BW, MAX_BW );
				int distance = this.randomMinMaxNumber( MIN_DISTANCE, MAX_DISTANCE );
				SdnLink sdnLink = new SdnLink( nameNetwork, linksCount++, bw, distance );
				sdnLink.setFirstNode( sdnRouter1 );
				sdnLink.setSecondNode( sdnRouter2 );
				graph.addEdge( sdnLink, sdnRouter1, sdnRouter2 );
			}
		}
	}
	private boolean isConnected( SdnRouter sdnRouter1, SdnRouter sdnRouter2, Graph< Node, Link > graph )
	{
		for( Link link: graph.getEdges() )
			if( link.getFirstNode().equals( sdnRouter1 ) && link.getSecondNode().equals( sdnRouter2 ) ||
				link.getFirstNode().equals( sdnRouter2 ) && link.getSecondNode().equals( sdnRouter1 )	)
				return true;
		
		return false;
	}
	
	public SdnNetwork createSdnNetwork()
	{
		String nameNetwork = NAME_PREFIX;
		Graph< Node, Link > graph = new NetworkGraph();
		int nodeCount = 0;
		
		int sdnRoutersNumber = this.randomMinMaxNumber( MIN_ROUTERS_NUMBER, MAX_ROUTERS_NUMBER );
		List< SdnRouter > sdnRouters = new ArrayList< SdnRouter >();
		
		for( int j = 0; j < sdnRoutersNumber; j++ )
		{
			String os = this.randomOS( LINUX_PERCENTAGE, WINDOWS_PERCENTAGE, SOLARIS_PERCENTAGE, ANDROID_PERCENTAGE );
			int cpu = this.randomMinMaxNumber( MIN_CPU, MAX_CPU );
			int ram = this.randomMinMaxNumber( MIN_RAM, MAX_RAM );
			int virtualNodesNumber = this.randomMinMaxNumber( MIN_VIRTUAL_NODES_NUMBER, MAX_VIRTUAL_NODES_NUMBER );
			SdnRouter sdnRouter = new SdnRouter( nameNetwork, nodeCount++, os, cpu, ram, virtualNodesNumber );
			sdnRouters.add( sdnRouter );
			graph.addVertex( sdnRouter );
		}
		
		this.connectSdnRouters( graph, sdnRouters, nameNetwork );
					
		int cpuPrice = this.randomMinMaxNumber( MIN_CPU_PRICE, MAX_CPU_PRICE );
		int ramPrice = this.randomMinMaxNumber( MIN_RAM_PRICE, MAX_RAM_PRICE );
		int storagePrice = this.randomMinMaxNumber( MIN_STORAGE_PRICE, MAX_STORAGE_PRICE );	
		int bwPrice = this.randomMinMaxNumber( MIN_BW_PRICE, MAX_BW_PRICE );
		
		return new SdnNetwork( nameNetwork, 0, graph, cpuPrice, ramPrice, storagePrice, bwPrice );
	}
}
